var doc = window.document;

// Object原生hasOwnProperty的捷径
var hasOwn = Object.prototype.hasOwnProperty,
    // Object原生toString的捷径
    toString = Object.prototype.toString,
    // String原生substring的捷径
    substring = String.prototype.substring,
    // Array原生slice的捷径
    slice = Array.prototype.slice,
    // Array原生splice的捷径
    splice = Array.prototype.splice,
    // Array原生forEach的捷径
    forEach = Array.prototype.forEach,
    // 四舍五入的捷径
    round = Math.round,
    // 取绝对值的捷径
    abs = Math.abs,
    // 向上取整的捷径
    ceil = Math.ceil,
    // 向下取整的捷径
    floor = Math.floor,
    // 编码
    encode = encodeURIComponent,
    // 解码
    decode = decodeURIComponent;

var events = {};
// 浏览器事件监听、解除监听函数
var domEvent = {
    on: function (el, type, fn) {
        if (!el || !type || !fn) {
            return;
        }
        var handlers = events[type] || (events[type] = []);
        handlers.push({el: el, fn: fn});
        domEvent.add(el, type, fn);
    },
    off: function (el, type, fn) {
        if (!el || !type) {
            return;
        }
        var handlers = events[type];
        each(handlers, function (handler, i) {
            if (handler) {
                var el = handler.el,
                    fn = handler.fn;
                handlers[i] = null;
                domEvent.remove(el, type, fn);
            }
        });
    },
    add: function (el, type, fn) {
        el.addEventListener(type, fn, false);
    },
    remove: function (el, type, fn) {
        el.removeEventListener(type, fn, false);
    }
};

var isArray = Array.isArray;

if (!isArray) {
    isArray = function() {
        return toString.call(obj) === '[object Array]';
    }
}

// 判断对象是否未定义
function isUndef() {
    var args = arguments,
        count = args.length;
    if (count < 2) {
        return typeof(args[0]) === 'undefined';
    } else {
        var r = true;
        each(args, function (v) {
            if (typeof(v) !== 'undefined') {
                r = false;
                return {};
            }
        });
        return r;
    }
}

// 判断对象是否为空值
// 空值情况包括：未定义、null、空字符串、空对象（对象中没有键值）
function isBlank(obj) {
    var blank = true;
    if (isDef(obj) &&
        (obj !== null) &&
        (obj !== 'null') &&
        (obj !== '')) {
        blank = false;
    }
    if (!blank &&
        (isArray(obj) || isObject(obj))) {
        blank = true;
        each(obj, function (v) {
            if (isDef(v)) {
                blank = false;
                return breaker;
            }
        });
    }
    return blank;
}

// 判断对象是否为布尔值
function isBoolean(obj) {
    return (obj === true) || (obj === false);
}

// 判断对象是否为函数
function isFunction (obj) {
    return toString.call(obj) === '[object Function]';
}

// 判断对象是否为字符串
function isString(obj) {
    return toString.call(obj) === '[object String]';
}

// 判断对象是否为字符串
function isNumber(obj) {
    return toString.call(obj) === '[object Number]';
}

// 判断对象是否为Object，这里的Object为狭义上的Object
function isObject(obj) {
    return (obj === Object(obj)) && !isArray(obj) && !isFunction (obj);
}
var breaker = false;
// 遍历方法，'each'的实现，就如'forEach'
// 调用类和数组的原生遍历方法
// 如果有**ECMAScript 5**原生的'forEach'方法则委托其处理
// 来源: underscore
function each(obj, fn, context) {
    if (isUndef(obj) || !isFunction (fn)) {
        return;
    }
    if (forEach && (obj.forEach === forEach)) {
        obj.forEach(fn, context);
    } else if (isArray(obj)) {
        for (var i = 0, l = size(obj); i < l; i++) {
            if (fn.call(context, obj[i], i, obj) === breaker) {
                return;
            }
        }
    } else if (isObject(obj)) {
        for (var key in obj) {
            if (hasOwn.call(obj, key)) {
                if (fn.call(context, obj[key], key, obj) === breaker) {
                    return;
                }
            }
        }
    }
}

// 字符串复制
function repeat(string, times) {
    return (times < 1) ? '' : (new Array(times + 1).join(string));
}

// 将对象转化为字符串并在前面补足0直到满足指定长度
function add0(obj, length) {
    if (!obj) {
        return "0";
    }
    var str = String(obj),
        times = length - size(str);
    return repeat("0", times) + str;
}

// 获取指定对象的子元素数量
function size(obj) {
    var s = 0;
    if (isUndef(obj) || (obj === null)) {
        s = 0;
    } else if (obj.length === +obj.length) {
        s = obj.length;
    } else if (isObject(obj)) {
        s = keys(obj).length;
    } else if (isNumber(obj) || isBoolean(obj)) {
        s = String(obj).length;
    }
    return s;
}

// 生成随机数
// 可以不指定参数（生成0～1）或指定最大值（生成0～最大值）或最小值和最大值（生成最小值～最大值）
// 使用原生产生随机数的方法
function random() {
    var r = Math.random(),
        args = arguments,
        length = size(args);
    if (length === 1) {
        r *= args[0];
    } else if (length === 2) {
        r = round(r * (args[1] - args[0])) + args[0];
    }
    return r;
}

// 生成随机数，length用于指定所产生结果的长度
// 如果type为空，则生成随机字符串（包含字母和数字）；如果type为‘num’，则生成随机数字字符串（仅包含数字）
function rand(length, type) {
    if (length < 1) {
        return 0;
    }
    var str = "",
        r,
        isNum = (type === "num");
    while (size(str) < length) {
        r = floor(Date.now() * random() * 999);
        if (!isNum) {
            r = r.toString(16);
        }
        str += r;
    }
    return str.substr(0, length);
}

var COOKIE_REG = /([\w_]+)\=([\w%-\.\=]+)/;

/**
 * 获取 cookie 对象
 * @return {Object} 格式化后的 cookies
 */
function getCookies(){
    var cookies = {};
    document.cookie.split("; ").forEach(function(item){
        var cookie = item.match(COOKIE_REG);
        if (cookie && cookie[1] && cookie[2]) {
            var val;
            try {
                val = decodeURIComponent(cookie[2]);
            } catch(e) {
                val = cookie[2];
            }
            cookies[cookie[1]] = val;
        }
    });
    return cookies;
}

// 生成唯一id
// 参数flag可以传入标记，将出现在第3组数据，建议只设置1位字符
function uuid(flag, useCid, nomark) {
    var id = "";
    if (useCid) {
        var cookies = getCookies();
        if (cookies.user_id) {
            return cookies.user_id;
        }
    }

    var pick = function (value, length) {
        var base16 = value.toString(16),
            len = size(base16) - 1,
            str = "";
        while (size(str) < length) {
            str += base16.substr(round(random(0, len)), 1);
        }
        return str;
    };

    var t = Date.now(),
        t16 = add0(t.toString(16), 12),
        u1 = t16.substr(0, 8),
        u2 = t16.substr(8, 4),
        u3 = pick(t, 4),
        u4 = pick(rand(t % 97), 2),
        u5 = pick(rand(t % 89), 2),
        u6 = pick(rand(4), 1) + pick(rand(8), 1) + pick(rand(16), 2) +
            pick(rand(32), 2) + pick(rand(128), 3);
    if (flag) {
        u3 = (flag + u3).substr(0, 4);
    }
    var idChar = (u1 + "-" + u2  + "-" + u3 + "-" + u4 + u5 + "-" + u6).split("");
    var txt;
    var r = random(nomark ? 0 : 3, 8);
    while(idChar.length) {
        txt = idChar.pop();
        if (r > Math.random()*10) {
            txt = txt.toUpperCase();
        }
        id += txt;
    }
    id = (nomark ? "" : "Pi-") + id;
    return id;
}

function extend(){
    var args = slice.call(arguments);
    var dat = args.shift();
    if (dat) {
        if (args.length) {
            while(args.length) {
                var item = args.shift();
                if (item) {
                    each(item, function(val, key){
                        if (isObject(dat[key]) && isObject(val)) {
                            extend(dat[key], val);
                        } else {
                            dat[key] = val;
                        }
                    });
                }
            }
        }
        return dat;
    }

    return null;
}

function toParam(data) {
    var str = [];
    for (var n in data) {
        if (hasOwn.call(data, n)) {
            str.push(
                n+"="+encodeURIComponent(data[n])
            );
        }
    }
    return str.join("&");
}

/**
 * 格式化数字, 自动补0前续
 * @param  {Number} number 要格式化的数字
 * @param  {Number} size   格式化后出来的数字位数
 * @return {String}        格式化结果
 */
function fix0(number, size){
    number = number.toString();
    while (number.length < size){
        number = "0" + number;
    }
    return number;
}

var format_exp = /[YymndjNwaAghGHis]/g;
/**
 * 格式化时间
 * @param  {String} format 日期格式
 * @param  {Mix}    date   日期数据(时间戳, 字符串, Date对象, 空)
 * @param  {Int}    offset 偏移修正值
 * @return {String}        格式化后的时间
 */
var dateformat = function(format,ts){
    if(typeof(format) === "number" && !ts){
        ts = format;
        format = "Y-m-d";
    }
    ts = new Date(ts);
    return format.replace(format_exp,function(tag){
        switch (tag){
            case "Y": return ts.getFullYear();
            case "y": return ts.getFullYear() % 100;
            case "m": return fix0(ts.getMonth()+1, 2);
            case "n": return ts.getMonth()+1;
            case "d": return fix0(ts.getDate(), 2);
            case "j": return ts.getDate();
            case "N": return ts.getDay();
            case "w": return ts.getDay() % 7;
            case "a": return ts.getHours() < 12 ? "am":"pm";
            case "A": return ts.getHours() < 12 ? "AM":"PM";
            case "g": return ts.getHours() % 12 + 1;
            case "h": return fix0(ts.getHours() % 12 + 1, 2);
            case "G": return ts.getHours();
            case "H": return fix0(ts.getHours(), 2);
            case "i": return fix0(ts.getMinutes(), 2);
            case "s": return fix0(ts.getSeconds(), 2);
        }
        return tag;
    });
};

/**
 * 带花括号标签检测正则
 * @type {RegExp}
 */
var labelReplaceExp = /\{(\w+)\}/g;
/**
 * 批量替换字符串中带花括号标签为指定数据
 * @param  {String} tpl  待处理的字符串
 * @param  {Object} data 替换数据
 * @return {String}      替换后端字符串
 */
function labelReplace (tpl, data) {
    return tpl.replace(labelReplaceExp, function (label, key) {
        return isObject(data) ? data[key] : data;
    });
}

/**
 * 延迟一段时间执行方法
 * @param  {Function} fn   执行函数
 * @param  {Int}      wait 延迟时间
 * @return {Int}           计时器 id
 */
function delay(fn, wait) {
    var args = slice.call(arguments, 2);
    return setTimeout(function () {return fn.apply(null, args); }, wait);
}

// 延迟执行函数，让浏览器有机会喘口气
function defer(fn) {
    return delay.apply(this, [fn, 1].concat(slice.call(arguments, 1)));
}

var storage = window.localStorage;
var sStorage = sessionStorage;

var sessionStore = {
    /**
     * 获取缓存数据
     * @param  {String} key 数据字段
     * @return {Mix}        存储的数据
     */
    get: function(key) {
        var dat = sStorage.getItem(key);
        if (dat) {
            try {
                dat = JSON.parse(dat);
            } catch(e) {
                dat = null;
            }
            if (dat) {
                if (dat.expire < 0 || dat.expire >= Date.now()) {
                    return dat.data;
                }
                return null;
            }
            return null;
        } else {
            return null;
        }
    }
    /**
     * 存储数据
     * @param  {String} key    存储字段名
     * @param  {Mix}    data   要存储的数据
     * @param  {Int}    expire 过期时间
     * @return {Mix}           存储的数据
     */
    ,set: function(key, data, expire) {
        expire = isNumber(expire) && expire > 0 ? Math.ceil(expire) * 1000 : -1;
        if (isObject(data)) {
            try {
                data = JSON.stringify(data);
            } catch(e) {
                data = null;
            }
        }
        if (data) {
            var expireTs;
            if (expire > 0) {
                expireTs = Date.now() + expire;
            } else {
                expireTs = expire;
            }
            var dat = sStorage.getItem(key);
            if (dat) {
                try {
                    dat = JSON.parse(dat);
                } catch(e) {
                    dat = null;
                }
                if (dat) {
                    dat.expire = expireTs;
                    dat.data = data;
                } else {
                    dat = {
                        "expire": expireTs
                        ,"data": data
                    }
                }
            } else {
                dat = {
                    "expire": expireTs
                    ,"data": data
                }
            }
            sStorage.setItem(key, JSON.stringify(dat));
            return dat;
        } else {
            return null;
        }
    }
    /**
     * 删除
     * @param  {String} key 要删除的字段
     * @return {Mix}        被删除的数据
     */
    ,del: function(key) {
        var dat = sStorage.getItem(key);
        sStorage.removeItem(key);
        return dat;
    }
}

var UA = window.navigator.userAgent;

// 平台信息对象
var plat = {};

// 平台、设备信息
var osAndDevice = {
    // 操作系统
    // "os": ""
    // 设备标示
    // ,"device": ""
    // 操作系统版本
    // ,"osVersion": ""
    // 平台版本
    // ,"pfVersion": ""
    // 平台类型
    // ,"pf": ""
};

(function (){

    // 平台检测正则对象
    var UA_REG_EXPS = {
        "wechat": /MicroMessenger\/([\d.]+)/
        ,"alipay": /AlipayClient\/([\d.]+)/
        ,"weibo": /__weibo__([\d.]+)/
    };

    // 各种正则
    var android = UA.match(/(Android)\s+([\d.]+)/);
    var ipad = UA.match(/(iPad).*OS\s([\d_]+)/);
    var iphone = !ipad && UA.match(/(iPhone\sOS)\s([\d_]+)/);
    var androidDeviceRegExp = /Android\s+[\d\.]+;\s+(.*);/;
    var commonOsRegExp = /\([^\)]*\)/;

    // 设备名称
    var device;
    var unknownStr = "Unknown";
    var deviceInfo;

    // 操作系统
    // 现在只处理安卓跟 iOS
    if (android) {
        device = UA.match(androidDeviceRegExp);
        device = device ? device[1] || unknownStr : unknownStr;
        osAndDevice.osVersion = android && android[2] ? android[2] : unknownStr;
        osAndDevice.os = "Android";
    } else {
        device = iphone ? "iPhone" : ipad ? "iPad" : unknownStr;
        deviceInfo = (ipad || iphone);
        osAndDevice.os = deviceInfo ? "iOS" : unknownStr;
        osAndDevice.osVersion = deviceInfo && deviceInfo[2] ? deviceInfo[2] : unknownStr;
        // 获取到的ios的版本号以"_"分割，转换为以"."分割
        osAndDevice.osVersion = osAndDevice.osVersion.replace(/_/g, ".");
    }

    if (!osAndDevice.os || osAndDevice.os === unknownStr) {
        // window 平台内部版本与发行版的映射关系
        var windowVersionMap = {
            "5.0": "Windows 2000"
            ,"5.1": "Windows XP"
            ,"6.0": "Windows Vista"
            ,"6.1": "Windows 7"
            ,"6.2": "Windows 8"
            ,"6.3": "Windows 8.1"
            ,"10": "Windows 10"
            ,"10.0": "Windows 10"
        };

        device = UA.match(commonOsRegExp);
        device = device && device[0];
        var osInfo;
        var osVInfo;
        var isPc = false;
        if (device) {
            osInfo = device.replace(/[\(|\)]+/, "").split(/;[\s]?/);
            if (osInfo[1] === "U") {
                osInfo.splice(1, 1);
            }
            switch(true) {
                case device.indexOf("Win") !== -1:
                    osAndDevice.os = "Windows";
                    isPc = true;
                    var winOs = "";
                    while(osInfo.length) {
                        winOs = osInfo.shift();
                        if (winOs.indexOf("Win") !== -1) {
                            break;
                        }
                    }
                    if (winOs) {
                        osVInfo = winOs.match(/[\d\.]+/);
                        osVInfo = osVInfo && osVInfo[0] ? windowVersionMap[osVInfo[0]] || unknownStr : unknownStr;
                    } else {
                        osVInfo = unknownStr;
                    }
                    osAndDevice.osVersion = osVInfo;
                break;

                case device.indexOf("Mac OS") !== -1:
                    osAndDevice.os = "Mac OS";
                    osVInfo = device.match(/[\d_]+/);
                    osVInfo = osVInfo && osVInfo[0] || unknownStr;
                    osVInfo = osVInfo.replace(/_/g, ".");
                    isPc = true;
                    osAndDevice.osVersion = osVInfo;
                break;

                case device.indexOf("Linux") !== -1:
                    osAndDevice.os = "Linux";
                    isPc = true;
                    osAndDevice.osVersion = unknownStr;
                break;
            }
        }

        // ie 内核映射
        var ieTridentMap = {
            "7.0": "11"
            ,"6.0": "10"
            ,"5.0": "9"
            ,"4.0": "8"
        };

        var pfInfo;
        switch(true) {
            // Safari
            // 呆逼 Chrome 会带有 Safari 的版本信息
            case (pfInfo = UA.match(/Safari\/([\d\.]+)/)) !== null && UA.indexOf("Chrome") === -1:
                osAndDevice.pf = "Safari";
                osAndDevice.pfVersion = pfInfo[1];
            break;

            // Chrome
            // 呆逼 Edge and Opera 会加上各种奇怪的浏览器声明
            case (pfInfo = UA.match(/Chrome\/([\d\.]+)/)) !== null && UA.indexOf("Edge") === -1 && UA.indexOf("OPR") === -1:
                osAndDevice.pf = "Chrome";
                osAndDevice.pfVersion = pfInfo[1];
            break;

            // 火狐
            case (pfInfo = UA.match(/Gecko\/[\d]+\sFirefox\/([\d\.]+)/)) !== null:
                osAndDevice.pf = "Firefox";
                osAndDevice.pfVersion = pfInfo[1];
            break;

            // Opera
            case (pfInfo = UA.match(/(Opera|OPR)\/([\d\.]+)/)) !== null:
                osAndDevice.pf = "Opera";
                osAndDevice.pfVersion = pfInfo[2];
            break;

            // Edge 之前的 ie
            // 呆逼 Opera 老版本会带有 MSIE 这个特征
            case UA.indexOf("Opera") === -1 && (pfInfo = UA.match(/MSIE[\/\s]?([\d\.]+)/)) !== null:
                osAndDevice.pf = "IE";
                osAndDevice.pfVersion = pfInfo[1];
            break;

            // 只有内核声明的情况，使用 Trident 的版本来判断
            case UA.indexOf("MSIE") === -1 && (pfInfo = UA.match(/Trident[\/\s]?([\d\.]+)/)) !== null:
                osAndDevice.pf = "IE";
                // 使用映射表查找对应的版本
                osAndDevice.pfVersion = ieTridentMap[pfInfo[1]];
            break;

            // Edge
            case (pfInfo = UA.match(/Edge\/([\d\.]+)/)) !== null:
                osAndDevice.pf = "Edge";
                osAndDevice.pfVersion = pfInfo[1];
            break;
        }
        osAndDevice.pfVersion = osAndDevice.pfVersion || unknownStr;
        pfInfo = null;
        osInfo = null;
        osVInfo = null;
    }

    osAndDevice.device = isPc ? "PC" : device;

    if (!osAndDevice.pf) {
        // 根据平台检测正则对象生成平台信息对象
        each(UA_REG_EXPS, function(reg, key){
            var re = UA.match(reg);
            plat[key] = re ? re[1] : false;
            if (re) {
                osAndDevice.pfVersion = re[1];
                osAndDevice.pf = key;
            }
            re = null;
        });
    }


    if (!osAndDevice.pfVersion) {
        osAndDevice.pfVersion = unknownStr;
        osAndDevice.pf = unknownStr;
    }

    UA_REG_EXPS = null;
    android = ipad = iphone = androidDeviceRegExp = device = null;
})();


/**
 * 获取 url 上的参数
 * @param  {String} key    要获取的参数
 * @param  {String} search 参数字符串
 * @return {Mix}           指定的参数值或参数对象
 */
function getQuery(key, search) {
    search = search || window.location.search;
    search = search.replace("?", "");
    if (search) {
        search = search.split("&");
    } else {
        search = [];
    }
    var query = {};
    each(search, function(val){
        var queryArr = val.split("=");
        query[queryArr[0]] = decodeURIComponent(queryArr[1]);
    });
    return key ? query[key] : query;
}
